#include <stdint.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <iostream>
#include <sys/mman.h>

unsigned long mem_virt2phy(const void *virtaddr)
{
	int fd, retval;
	uint64_t page, physaddr;
	unsigned long virt_pfn;
	int page_size;
	off_t offset;

	/* standard page size */
	page_size = getpagesize();

	fd = open("/proc/self/pagemap", O_RDONLY);
	if (fd < 0) {
		std::cout<< "cannot open /proc/self/pagemap" << std::endl;
		return 0;
	}

	virt_pfn = (unsigned long)virtaddr / page_size;
	offset = sizeof(uint64_t) * virt_pfn;
    off_t offset_res = lseek(fd, offset, SEEK_SET);
	if (offset_res == (off_t) -1) {
		std::cout<< "seek error in /proc/self/pagemap"<< std::endl;
		close(fd);
	}

    int expect_size = sizeof(long);
	retval = read(fd, &page, expect_size);
	close(fd);
	if (retval < 0) {
		std::cout<< "cannot read /proc/self/pagemap" << std::endl;
		return 0;
	} else if (retval != expect_size) {
		std::cout<< "read " << retval << " bytes from /proc/self/pagemap, "
				"but expected " << expect_size << std::endl;
        std::cout<< "expect offset = " << offset << ", result offset = "
                << offset_res << std::endl;
		return 0;
	}

	/*
	 * the pfn (page frame number) are bits 0-54 (see
	 * pagemap.txt in linux Documentation)
	 */
	if ((page & 0x7fffffffffffffULL) == 0)
    {
        std::cout<< "abnormal page = " << page << std::endl;
        return 0;
    }
		

	physaddr = ((page & 0x7fffffffffffffULL) * page_size)
		+ ((unsigned long)virtaddr % page_size);

	return physaddr;
}

int main(void)
{
    int x = 123456789;
    unsigned long physaddr = mem_virt2phy(&x);
    std::cout<< "x = " << x << ", physaddr = " << std::hex << physaddr << std::dec << std::endl;

    //通过/dev/mem直接访问物理地址
    int fd = open("/dev/mem", O_RDWR);
    if (fd < 0) {
        std::cout<< "cannot open /dev/mem" << std::endl;
        return 0;
    }
    int page_size = getpagesize();
    unsigned long offset = physaddr / page_size * page_size;
    //正式发行的linux系统通常会开CONFIG_STRICT_DEVMEM选项，所以mmap会失败
    void *ptr = mmap(NULL, page_size, PROT_READ, MAP_SHARED, fd, offset);
    if (ptr == MAP_FAILED) {
        std::cout<< "cannot mmap /dev/mem" << std::endl;
    }
    else {
        int *p = (int *)((unsigned long)ptr + (physaddr % page_size));
        std::cout<< "x = " << *p << std::endl;
        munmap(ptr, page_size);
    }

    close(fd);
}